#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Ford车型参数爬虫
使用方法: python get_aModel.py model=puma

# 爬取Puma车型
python get_aModel.py model=puma

# 爬取其他车型（如Focus）
python get_aModel.py model=focus

# 爬取Kuga车型
python get_aModel.py model=kuga


"""

import json
import sys
import time
import os
import random
from urllib.parse import urljoin
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.chrome.options import Options
from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from bs4 import BeautifulSoup

class FordModelScraper:
    def __init__(self):
        self.series_list = self.load_series_list()
        self.param_translations = self.load_param_translations()
        self.driver = None
        
    def get_random_user_agent(self):
        """获取随机User-Agent"""
        user_agents = [
            "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
            "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36",
            "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.1 Safari/605.1.15",
            "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
            "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0",
            "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
        ]
        return random.choice(user_agents)
        
    def load_series_list(self):
        """加载车系列表"""
        try:
            with open('series_list.json', 'r', encoding='utf-8') as f:
                return json.load(f)
        except FileNotFoundError:
            print("错误: 找不到 series_list.json 文件")
            sys.exit(1)
        except json.JSONDecodeError:
            print("错误: series_list.json 文件格式错误")
            sys.exit(1)
    
    def load_param_translations(self):
        """加载参数翻译对照表"""
        try:
            with open('paramset_en_cn.json', 'r', encoding='utf-8') as f:
                return json.load(f)
        except FileNotFoundError:
            print("错误: 找不到 paramset_en_cn.json 文件")
            sys.exit(1)
        except json.JSONDecodeError:
            print("错误: paramset_en_cn.json 文件格式错误")
            sys.exit(1)
    
    def find_model_url(self, model_name):
        """根据车型名称查找URL"""
        model_name_lower = model_name.lower()
        
        for series in self.series_list:
            if series['name'].lower() == model_name_lower:
                return series['url']
        
        print(f"错误: 未找到车型 '{model_name}'")
        print("可用的车型:")
        for series in self.series_list:
            print(f"  - {series['name']}")
        return None
    
    def setup_driver(self):
        """设置Chrome驱动"""
        chrome_options = Options()
        chrome_options.add_argument("--headless")  # 无头模式
        chrome_options.add_argument("--no-sandbox")
        chrome_options.add_argument("--disable-dev-shm-usage")
        chrome_options.add_argument("--disable-gpu")
        chrome_options.add_argument("--window-size=1920,1080")
        chrome_options.add_argument(f"--user-agent={self.get_random_user_agent()}")
        
        # 反反爬虫设置
        chrome_options.add_experimental_option("excludeSwitches", ["enable-automation"])
        chrome_options.add_experimental_option('useAutomationExtension', False)
        chrome_options.add_argument("--disable-blink-features=AutomationControlled")
        
        service = Service(ChromeDriverManager().install())
        driver = webdriver.Chrome(service=service, options=chrome_options)
        
        # 隐藏webdriver特征
        driver.execute_script("Object.defineProperty(navigator, 'webdriver', {get: () => undefined})")
        
        return driver
    
    def extract_colors(self, url):
        """提取车型的所有颜色信息"""
        print("提取颜色信息...")
        
        try:
            # 使用当前的driver实例
            html = self.driver.page_source
            soup = BeautifulSoup(html, 'html.parser')
            
            # 查找颜色选择器
            color_lists = soup.find_all('ul', class_='_colouriserControlList_1vxwq_8')
            
            if not color_lists:
                print("❌ 未找到颜色选择器")
                return []
            
            colors = []
            for color_list in color_lists:
                # 查找颜色项
                color_items = color_list.find_all('span', class_='_colouriserControlTriggerInner_1vxwq_63')
                
                for item in color_items:
                    # 查找图片和alt文本
                    img = item.find('img', class_='_colouriserControlSwatch_1vxwq_140')
                    if img:
                        color_name = img.get('alt', '').strip()
                        if color_name:
                            colors.append(color_name)
            
            print(f"✅ 成功提取 {len(colors)} 种颜色: {', '.join(colors)}")
            return colors
            
        except Exception as e:
            print(f"❌ 提取颜色时出错: {e}")
            return []
    
    def extract_model_params(self, url):
        """提取指定URL的所有车型参数"""
        models_url = url + "#models"
        print(f"访问页面: {models_url}")
        
        self.driver = self.setup_driver()
        
        try:
            self.driver.get(models_url)
            wait = WebDriverWait(self.driver, 15)
            
            # 随机等待时间
            wait_time = random.uniform(3, 7)
            time.sleep(wait_time)
            
            # 滚动到页面底部确保所有内容加载
            self.driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
            time.sleep(2)
            
            # 首先提取颜色信息
            colors = self.extract_colors(url)
            
            # 获取所有轮播中的车型参数
            print("开始获取轮播中的所有车型...")
            all_models_data = self.get_all_carousel_models(colors)
            
            return all_models_data
            
        except Exception as e:
            print(f"错误: {e}")
            return []
        finally:
            if self.driver:
                self.driver.quit()
    
    def get_all_carousel_models(self, colors):
        """获取轮播中的所有车型数据 - 使用 slick-next-btn 翻页策略"""
        all_models_data = []
        
        # 查找轮播容器
        try:
            carousel_container = self.driver.find_element(By.CSS_SELECTOR, "div.carousel-cards-container")
            print("✅ 找到轮播容器")
        except:
            print("❌ 未找到轮播容器，使用传统方法")
            return self.get_models_traditional_way(colors)
        
        print("开始使用 slick-next-btn 翻页策略...")
        
        processed_models = set()  # 用于去重
        page_count = 0
        max_pages = 10  # 防止无限循环
        
        while page_count < max_pages:
            page_count += 1
            print(f"\n=== 轮播页面 {page_count} ===")
            
            # 等待页面稳定
            time.sleep(2)
            
            # 获取当前页面的所有活跃 slide
            current_slides = self.driver.find_elements(By.CSS_SELECTOR, ".slick-slide.slick-active")
            print(f"当前页面找到 {len(current_slides)} 个活跃 slide")
            
            page_has_new_models = False
            
            # 处理当前页面的每个 slide
            for i, slide in enumerate(current_slides):
                try:
                    # 检查slide是否有实际内容
                    model_name = ""
                    model_co_name = ""
                    has_button = False
                    
                    try:
                        title_elem = slide.find_element(By.CLASS_NAME, "card-series-title")
                        model_name = title_elem.text.strip()
                    except:
                        continue
                    
                    try:
                        powertrain_elem = slide.find_element(By.CLASS_NAME, "powertrain-label")
                        model_co_name = powertrain_elem.text.strip()
                    except:
                        continue
                    
                    try:
                        button = slide.find_element(By.CSS_SELECTOR, "button.full-spec-cta")
                        has_button = button.is_displayed()
                    except:
                        continue
                    
                    # 只处理有完整内容的slide
                    if model_name and model_co_name and has_button:
                        # 创建唯一标识符来避免重复
                        unique_id = f"{model_name}|{model_co_name}"
                        
                        if unique_id not in processed_models:
                            processed_models.add(unique_id)
                            page_has_new_models = True
                            
                            print(f"  处理新车型 {i+1}: '{model_name}' - '{model_co_name[:60]}...'")
                            
                            try:
                                # 滚动到slide位置
                                self.driver.execute_script("arguments[0].scrollIntoView(true);", slide)
                                time.sleep(1)
                                
                                # 点击按钮
                                button = slide.find_element(By.CSS_SELECTOR, "button.full-spec-cta")
                                self.driver.execute_script("arguments[0].click();", button)
                                time.sleep(3)
                                
                                # 等待并查找full-specs-content
                                specs_content = self.wait_for_specs_content()
                                
                                if specs_content:
                                    # 提取参数
                                    params, real_model_name = self.extract_params_from_content(specs_content)
                                    
                                    # 使用从模态框获取的真正车型名称，如果没有则使用卡片上的名称
                                    final_model_name = real_model_name if real_model_name else model_name
                                    
                                    if params:
                                        model_data = {
                                            "model": final_model_name,
                                            "model_co_name": model_co_name,
                                            "colorSet": colors,
                                            "paramSet": params
                                        }
                                        all_models_data.append(model_data)
                                        print(f"    ✅ 成功提取 {len(params)} 个参数")
                                    else:
                                        print(f"    ❌ 未能提取到参数")
                                else:
                                    print(f"    ❌ 未找到参数内容")
                                
                                # 关闭可能的弹出框
                                self.close_specs_modal()
                                time.sleep(1)
                                
                            except Exception as e:
                                print(f"    ❌ 处理车型时出错: {e}")
                                self.close_specs_modal()
                                continue
                        else:
                            print(f"  跳过重复车型: '{model_name}' - '{model_co_name[:60]}...'")
                    
                except Exception as e:
                    continue
            
            if not page_has_new_models:
                print("当前页面无新车型")
            
            # 检查 slick-next-btn 是否可用
            try:
                next_button = self.driver.find_element(By.CSS_SELECTOR, "button.slick-next-btn")
                is_disabled = next_button.get_attribute("disabled") is not None
                button_classes = next_button.get_attribute("class")
                
                if is_disabled or "disabled" in button_classes:
                    print("🛑 slick-next-btn 已禁用，轮播结束")
                    break
                
                # 点击 next 按钮
                print("� 点击 slick-next-btn 进入下一页...")
                self.driver.execute_script("arguments[0].click();", next_button)
                time.sleep(3)  # 等待轮播动画完成
                
            except Exception as e:
                print(f"❌ 处理 slick-next-btn 时出错: {e}")
                break
        
        print(f"\n📊 轮播翻页完成，共访问 {page_count} 页，获取 {len(all_models_data)} 个独特车型配置")
        
        # 显示所有获取到的车型
        print("\n📋 获取到的车型列表:")
        for i, model in enumerate(all_models_data, 1):
            print(f"  {i}. {model['model']} - {model['model_co_name'][:80]}...")
        
        return all_models_data
    
    def get_visible_spec_buttons(self):
        """获取当前可见的See Full Specs按钮"""
        try:
            all_buttons = self.driver.find_elements(By.CSS_SELECTOR, "button.full-spec-cta.tertiary-cta")
            visible_buttons = [btn for btn in all_buttons if btn.is_displayed()]
            print(f"找到 {len(visible_buttons)} 个可见的规格按钮")
            return visible_buttons
        except Exception as e:
            print(f"获取可见按钮时出错: {e}")
            return []
    
    def slide_carousel_next(self):
        """滑动轮播到下一页"""
        try:
            # 方法1: 使用JavaScript滚动
            try:
                self.driver.execute_script("document.querySelector('.carousel-cards-container').scrollLeft += 400;")
                print("✅ 使用JavaScript滚动成功")
                return True
            except:
                pass
            
            # 方法2: 查找并点击next按钮
            next_button_selectors = [
                "button.carousel-next-btn",
                "button[class*='next']",
                ".slick-next"
            ]
            
            for selector in next_button_selectors:
                try:
                    buttons = self.driver.find_elements(By.CSS_SELECTOR, selector)
                    for button in buttons:
                        if button.is_displayed() and button.is_enabled():
                            self.driver.execute_script("arguments[0].click();", button)
                            print(f"✅ 点击next按钮成功: {selector}")
                            return True
                except:
                    continue
            
            # 方法3: 使用ActionChains滑动
            try:
                from selenium.webdriver.common.action_chains import ActionChains
                carousel = self.driver.find_element(By.CSS_SELECTOR, "div.carousel-cards-container")
                actions = ActionChains(self.driver)
                actions.move_to_element(carousel)
                actions.click_and_hold()
                actions.move_by_offset(-300, 0)  # 向左滑动
                actions.release()
                actions.perform()
                print("✅ 使用ActionChains滑动成功")
                return True
            except Exception as e:
                print(f"ActionChains滑动失败: {e}")
            
            print("❌ 所有滑动方法都失败")
            return False
            
        except Exception as e:
            print(f"滑动轮播时出错: {e}")
            return False
    
    def get_models_traditional_way(self, colors):
        """传统方式获取车型（当轮播功能失效时的备用方案）"""
        print("使用传统方式获取车型...")
        
        # 查找所有的See Full Specs按钮
        print("查找车型参数按钮...")
        spec_buttons = self.driver.find_elements(By.CSS_SELECTOR, "button.full-spec-cta.tertiary-cta")
        print(f"找到 {len(spec_buttons)} 个车型")
        
        if not spec_buttons:
            print("错误: 未找到任何车型参数按钮")
            return []
        
        all_models_data = []
        
        # 遍历每个车型
        for i, button in enumerate(spec_buttons):
            print(f"\n处理第 {i+1}/{len(spec_buttons)} 个车型...")
            
            try:
                # 获取按钮所在卡片的powertrain-label
                model_co_name = "Unknown Co-name"
                try:
                    card_container = button.find_element(By.XPATH, "./ancestor::div[contains(@class, 'card-container')]")
                    powertrain_elem = card_container.find_element(By.CLASS_NAME, "powertrain-label")
                    model_co_name = powertrain_elem.text.strip()
                    print(f"获取到powertrain-label: {model_co_name}")
                except Exception as e:
                    print(f"⚠️ 获取powertrain-label失败: {e}")
                
                # 找到对应的车型名称 (临时)
                temp_model_name = self.get_model_name_from_button(button)
                
                # 滚动到按钮位置
                self.driver.execute_script("arguments[0].scrollIntoView(true);", button)
                time.sleep(1)
                
                # 点击按钮
                self.driver.execute_script("arguments[0].click();", button)
                time.sleep(3)
                
                # 等待并查找full-specs-content
                specs_content = self.wait_for_specs_content()
                
                if specs_content:
                    # 提取参数和真正的车型名称
                    params, real_model_name = self.extract_params_from_content(specs_content)
                    
                    # 使用真正的车型名称，如果没有获取到则使用临时名称
                    final_model_name = real_model_name if real_model_name else temp_model_name
                    print(f"最终车型名称: {final_model_name}")
                    
                    if params:
                        model_data = {
                            "model": final_model_name,
                            "model_co_name": model_co_name,  # 新增字段
                            "colorSet": colors,  # 添加颜色信息
                            "paramSet": params
                        }
                        all_models_data.append(model_data)
                        print(f"✅ 成功提取 {len(params)} 个参数和 {len(colors)} 种颜色")
                    else:
                        print("❌ 未能提取到参数")
                else:
                    print("❌ 未找到参数内容")
                
                # 关闭可能的弹出框
                self.close_specs_modal()
                time.sleep(1)
                
            except Exception as e:
                print(f"❌ 处理第 {i+1} 个车型时出错: {e}")
                continue
        
        return all_models_data
    
    def get_model_name_from_button(self, button):
        """从按钮元素获取车型名称 - 这个方法已废弃，现在从specs内容中获取"""
        # 这个方法现在只返回临时名称，真正的名称在extract_params_from_content中获取
        return f"Model_{int(time.time())}"
    
    def wait_for_specs_content(self):
        """等待参数内容加载"""
        selectors = [
            "div.full-specs-content",
            "div[class*='full-specs']",
            "div[class*='specs-content']",
            "div[class*='specification']"
        ]
        
        wait = WebDriverWait(self.driver, 10)
        
        for selector in selectors:
            try:
                element = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, selector)))
                return element
            except:
                continue
        
        return None
    
    def extract_params_from_content(self, specs_content):
        """从参数内容中提取参数和车型名称"""
        try:
            specs_html = specs_content.get_attribute('innerHTML')
            soup = BeautifulSoup(specs_html, 'html.parser')
            
            # 首先提取车型名称
            model_name = None
            series_label = soup.find('div', class_='series-label')
            if series_label:
                model_name = series_label.get_text().strip()
                print(f"从series-label获取到车型名称: {model_name}")
            
            params = []
            
            # 方法1: 查找表格行
            for row in soup.find_all('tr'):
                cells = row.find_all(['td', 'th'])
                if len(cells) >= 2:
                    param_name = cells[0].get_text().strip()
                    param_value = cells[1].get_text().strip()
                    if param_name and param_value:
                        param_cn = self.param_translations.get(param_name, param_name)
                        params.append({
                            "param_name_en": param_name,
                            "param_name_cn": param_cn,
                            "param_value": param_value
                        })
            
            # 方法2: 如果没有表格，查找包含冒号的文本
            if not params:
                for item in soup.find_all(['div', 'li', 'p', 'span']):
                    text = item.get_text().strip()
                    if ':' in text and len(text) < 200:
                        parts = text.split(':', 1)
                        if len(parts) == 2:
                            param_name = parts[0].strip()
                            param_value = parts[1].strip()
                            if param_name and param_value:
                                param_cn = self.param_translations.get(param_name, param_name)
                                params.append({
                                    "param_name_en": param_name,
                                    "param_name_cn": param_cn,
                                    "param_value": param_value
                                })
            
            return params, model_name
            
        except Exception as e:
            print(f"提取参数时出错: {e}")
            return [], None
    
    def close_specs_modal(self):
        """关闭参数弹出框"""
        try:
            # 尝试多种关闭方式
            close_selectors = [
                "button[class*='close']",
                "button[aria-label*='close']",
                ".modal-close",
                ".popup-close",
                "[data-dismiss]"
            ]
            
            for selector in close_selectors:
                try:
                    close_button = self.driver.find_element(By.CSS_SELECTOR, selector)
                    self.driver.execute_script("arguments[0].click();", close_button)
                    time.sleep(1)
                    return
                except:
                    continue
            
            # 如果没有找到关闭按钮，尝试按ESC键
            from selenium.webdriver.common.keys import Keys
            self.driver.find_element(By.TAG_NAME, 'body').send_keys(Keys.ESCAPE)
            
        except Exception as e:
            print(f"关闭弹出框时出错: {e}")
    
    def save_results(self, model_name, data):
        """保存结果到文件"""
        filename = f"{model_name.lower()}_param.json"
        
        try:
            with open(filename, 'w', encoding='utf-8') as f:
                json.dump(data, f, ensure_ascii=False, indent=2)
            print(f"\n✅ 结果已保存到: {filename}")
            return True
        except Exception as e:
            print(f"❌ 保存文件时出错: {e}")
            return False
    
    def scrape_model(self, model_name):
        """爬取指定车型的参数"""
        print(f"开始爬取车型: {model_name}")
        
        # 查找车型URL
        model_url = self.find_model_url(model_name)
        if not model_url:
            return False
        
        print(f"找到车型URL: {model_url}")
        
        # 提取参数
        models_data = self.extract_model_params(model_url)
        
        if models_data:
            # 保存结果
            self.save_results(model_name, models_data)
            
            print(f"\n📊 爬取完成，共获取 {len(models_data)} 个车型配置:")
            for model_data in models_data:
                print(f"  - {model_data['model']}: {len(model_data['paramSet'])} 个参数")
            
            return True
        else:
            print("❌ 未能获取到任何数据")
            return False

def main():
    """主函数"""
    if len(sys.argv) != 2:
        print("使用方法: python get_aModel.py model=车型名称")
        print("例如: python get_aModel.py model=puma")
        sys.exit(1)
    
    # 解析参数
    arg = sys.argv[1]
    if not arg.startswith('model='):
        print("错误: 参数格式应为 model=车型名称")
        sys.exit(1)
    
    model_name = arg.split('=', 1)[1].strip()
    if not model_name:
        print("错误: 请指定车型名称")
        sys.exit(1)
    
    # 创建爬虫实例并开始爬取
    scraper = FordModelScraper()
    success = scraper.scrape_model(model_name)
    
    sys.exit(0 if success else 1)

if __name__ == "__main__":
    main()
